///////////////////////////////////////////////////////
// Code author: Martin Lapierre, http://devinstinct.com
///////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace SubSonic
{
    /// <summary>
    /// Provides access to configuration files for ASP.NET, EXEs and DLLs.
    /// </summary>
    public static class ConfigurationProvider
    {
        #region Fields

        /// <summary>
        /// The cache of configuration resolvers as a pair of assembly-full-name/resolver.
        /// </summary>
        private static Dictionary<string, ConfigurationResolver> _resolvers = new Dictionary<string, ConfigurationResolver>();

        #endregion Fields


        #region Properties

        /// <summary>
        /// Gets the configuration resolver instance for the calling method.
        /// </summary>
        public static ConfigurationResolver CurrentInstance
        {
            [MethodImpl(MethodImplOptions.NoInlining)] // Make sure the property is not inlined by the compiler.
            get
            {
                // The call to Assembly.GetCallingAssembly() must be in this property.
                return GetResolver(Assembly.GetCallingAssembly());
            }
        }

        /// <summary>
        /// Gets the ConnectionStringsSection data for the current application's default configuration.
        /// </summary>
        /// <remarks>
        /// The method uses the configuration file overrides, if set.
        /// Otherwise, it tries to use the assembly configuration file and
        /// falls back to app.config or web.config if the configuration information is not found.
        /// </remarks>
        public static ConnectionStringSettingsCollection ConnectionStrings
        {
            [MethodImpl(MethodImplOptions.NoInlining)] // Make sure the property is not inlined by the compiler.
            get
            {
                // The call to Assembly.GetCallingAssembly() must be in this property.
                return GetResolver(Assembly.GetCallingAssembly()).ConnectionStrings;
            }
        }

        /// <summary>
        /// Gets the AppSettingsSection data for the current application's default configuration. 
        /// </summary>
        /// <remarks>
        /// The method uses the configuration file overrides, if set.
        /// Otherwise, it tries to use the assembly configuration file and
        /// falls back to app.config or web.config if the configuration information is not found.
        /// </remarks>
        public static NameValueCollection AppSettings
        {
            [MethodImpl(MethodImplOptions.NoInlining)] // Make sure the property is not inlined by the compiler.
            get
            {
                // The call to Assembly.GetCallingAssembly() must be in this property.
                return GetResolver(Assembly.GetCallingAssembly()).AppSettings;
            }
        }

        #endregion Properties


        #region Methods

        /// <summary>
        /// Retrieves a specified configuration section for the current application's default configuration. 
        /// </summary>
        /// <remarks>
        /// The method uses the configuration file overrides, if set.
        /// Otherwise, it tries to use the assembly configuration file and
        /// falls back to app.config or web.config if the configuration information is not found.
        /// </remarks>
        /// <typeparam name="TSection">The type of the section to retrieve.</typeparam>
        /// <param name="sectionName">The configuration section path and name.</param>
        /// <returns>
        /// The specified ConfigurationSection object. 
        /// Throws a ConfigurationErrorsException if the section does not exist.
        /// </returns>
        [MethodImpl(MethodImplOptions.NoInlining)] // Make sure the method is not inlined by the compiler.
        public static TSection GetSection<TSection>(string sectionName)
            where TSection : System.Configuration.ConfigurationSection
        {
            // The call to Assembly.GetCallingAssembly() must be in this property.
            return GetResolver(Assembly.GetCallingAssembly()).GetSection<TSection>(sectionName);
        }

        /// <summary>
        /// Finds a project configuration files from a specified path.
        /// </summary>
        /// <remarks>
        /// The method starts by searching the given path, then goes up the directory hierarchy until 
        /// it finds the configuration files. It stops looking if a project, solution or the root
        /// directory is reached.
        /// </remarks>
        /// <param name="path">
        /// A file path or a directory path. 
        /// A directory path must end with Path.DirectorySeparatorChar.
        /// </param>
        /// <returns>The configuration files found.</returns>
        [MethodImpl(MethodImplOptions.NoInlining)] // Make sure the method is not inlined by the compiler.
        public static string[] FindProjectConfigFiles(string path)
        {
            // The call to Assembly.GetCallingAssembly() must be in this property.
            return GetResolver(Assembly.GetCallingAssembly()).FindProjectConfigFiles(path);
        }

        /// <summary>
        /// Gets the configuration resolver from the cache for the given assembly.
        /// </summary>
        /// <param name="assembly">The assembly to get the configuration resolver for.</param>
        /// <returns>The configuration resolver for the given assembly.</returns>
        private static ConfigurationResolver GetResolver(Assembly assembly)
        {
            if (!_resolvers.ContainsKey(assembly.FullName))
                lock (typeof(ConfigurationResolver))
                {
                    if (!_resolvers.ContainsKey(assembly.FullName))
                        _resolvers.Add(assembly.FullName, new ConfigurationResolver(assembly));
                }
            return _resolvers[assembly.FullName];
        }

        #endregion Methods
    }
}
